/* This file is part of swapper project
 *
 * Copyright (C) 2020 The Swapper Project Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package com.swapper.regexp;

import java.util.Objects;
import java.util.Random;

/**
 * The regular expression quantifier node.
 * A certain number of repeated matches for regular expression node.
 * For example, in regular expression "a+b*c?",
 * "a+", "b*", and "c?" are quantifier nodes.
 */
final class QuantifierNode extends PatternNode {
  private final PatternNode node;
  private final Quantifier quantifier;

  /**
   * Public constructor.
   *
   * @param context    the generator context.
   * @param node       the regular expression node that is repeatedly matched.
   * @param quantifier the match quantifier.
   * @throws NullPointerException if {@code node} or {@code quantifier} is null.
   */
  public QuantifierNode(PatternReverser context, PatternNode node, Quantifier quantifier) {
    super(context);
    this.node = Objects.requireNonNull(node);
    this.quantifier = Objects.requireNonNull(quantifier);
  }

  /**
   * Weight of quantifier are calculated based on context.
   *
   * @return the quantifier weight with context.
   * @see PatternReverser#getHugeQuantifierLimit()
   */
  private int computeQuantifierWeight() {
    if (quantifier.isCouldBeNaturalHuge()) {
      int atLeastTimes = quantifier.atLeastTimes();
      int atMostTimesWithContext = context.getHugeQuantifierLimit();
      if (atMostTimesWithContext >= atLeastTimes) {
        return atMostTimesWithContext - atLeastTimes + 1;
      }
    }
    return quantifier.weight();
  }


  /**
   * Gets the random weight.
   * Influenced by {@link PatternReverser#getHugeQuantifierLimit()}.
   *
   * @return the random weight.
   * @see #computeQuantifierWeight()
   */
  @Override
  public int weight() {
    return node.weight() * computeQuantifierWeight();
  }

  /**
   * Generates a random string that matches the node.
   * I want to make sure that every time it's random.
   *
   * @param random the random generator.
   * @return a random string that matches the node.
   */
  @Override
  public String random(Random random) {
    StringBuilder builder = new StringBuilder();
    int quantifierWeight = computeQuantifierWeight();
    int times = quantifier.atLeastTimes() + random.nextInt(quantifierWeight);
    for (; times > 0; --times) {
      builder.append(node.random(random));
    }
    return builder.toString();
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o instanceof QuantifierNode) {
      QuantifierNode that = (QuantifierNode) o;
      return Objects.equals(node, that.node)
        && Objects.equals(quantifier, that.quantifier);
    }
    return false;
  }

  @Override
  public int hashCode() {
    return Objects.hash(node, quantifier);
  }
}
